/*
 * Copyright 2022 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.mantisrx.runtime.core;

import io.mantisrx.runtime.Config;
import io.mantisrx.runtime.Context;
import io.mantisrx.runtime.MantisJob;
import io.mantisrx.runtime.core.functions.FilterFunction;
import io.mantisrx.runtime.core.functions.FlatMapFunction;
import io.mantisrx.runtime.core.functions.KeyByFunction;
import io.mantisrx.runtime.core.functions.MapFunction;
import io.mantisrx.runtime.core.sinks.SinkFunction;
import io.mantisrx.runtime.core.sources.SourceFunction;

public interface MantisStream<T> {
    static <OUT> MantisStream<OUT> create(Context context) {
        return MantisStreamImpl.init();
    }

    /**
     *
     * Defines a source operation that generates elements for the stream using
     * the provided {@link SourceFunction}.
     *
     * This method defines a source operation that generates elements for the
     * stream using the provided {@link SourceFunction}. A source operation is a
     * starting operation that produces the initial set of elements in the stream.
     * The provided {@link SourceFunction} defines the logic to generate the
     * elements.
     *
     * The method returns a new {@link MantisStream} that starts with the elements
     * generated by the provided {@link SourceFunction}. The resulting stream can be
     * used to apply further operations on the generated elements, such as
     * filtering, mapping, or aggregation.
     *
     * @param sourceFunction the function that generates the elements for the
     * @param <OUT> the type of the elements in the stream.
     * @return a new {@link MantisStream} that starts with the elements generated
     */
    <OUT> MantisStream<OUT> source(SourceFunction<OUT> sourceFunction);

    /**
     * Defines a sink operation that writes the elements in the stream to an
     * external system, using the provided {@link SinkFunction}.
     *
     * This method defines a sink operation that writes the elements in the stream
     * to an external system, using the provided {@link SinkFunction}. A sink
     * operation is a terminal operation that does not produce a new stream, but
     * instead writes the elements in the stream to an external system. The
     * provided {@link SinkFunction} defines the logic to write the elements.
     *
     * @param sinkFunction a sink operator usually an SSE sink
     * @return a {@link Config} object that captures the essence of {@link MantisJob}
     */
    Config<T> sink(SinkFunction<T> sinkFunction);

    /**
     * Applies the provided {@link FilterFunction} to each element in the stream
     * and returns all elements that match {@code filterFn.apply()}. The function is
     * applied independently to each element in the stream.
     *
     * @param filterFn the function to apply on each element in the stream. Returns boolean
     * @return a new {@link MantisStream}
     */
    MantisStream<T> filter(FilterFunction<T> filterFn);

    /**
     * Applies the provided {@link MapFunction} to each element in the stream
     * and returns a new stream consisting of result elements. The function is
     * applied independently to each element in the stream.
     *
     * @param mapFn the function to apply to each element in the stream.
     * @param <OUT> the type of the output elements in the resulting stream.
     * @return a new {@link MantisStream}
     */
    <OUT> MantisStream<OUT> map(MapFunction<T, OUT> mapFn);

    /**
     * Applies the provided {@link FlatMapFunction} to each element in the stream
     * and returns a new stream consisting of result elements. The function is
     * applied independently to each element in the stream. Compared to
     * {@link MapFunction}, each element might produce zero, one, or more elements.
     *
     * @param flatMapFn the function to apply to each element in the stream.
     * @param <OUT> the type of the output elements in the resulting stream.
     * @return a new {@link MantisStream}
     */
    <OUT> MantisStream<OUT> flatMap(FlatMapFunction<T, OUT> flatMapFn);

    /**
     * Operators are chained by default meaning processed in the same stage
     * (worker-thread). Materialize breaks this chaining and all the pending
     * operators are combined into a single scalar stage.
     * Any subsequent operators will go into a new scalar stage [with chaining]
     *
     * @return a new {@link MantisStream} instance
     */
    MantisStream<T> materialize();

    /**
     * Partitions the elements of the stream based on the key extracted by
     * {@link KeyByFunction}. Elements with the same key are assigned to the same partition.
     * This keyBy is distributed and each key is handled on a single worker possibly
     * handling many key groups.
     *
     * @param keyFn the function to extract the key from each element in the stream.
     * @param <K> the type of the key.
     * @return a new {@link KeyedMantisStream} with elements partitioned based on the keys.
     */
    <K> KeyedMantisStream<K, T> keyBy(KeyByFunction<K, T> keyFn);

}
